//#define USE_DUNGEONBUDDY_DLL
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using CommonBehaviors.Actions;
using Styx;
using Styx.Common.Helpers;
using Styx.CommonBot;
using Styx.CommonBot.Frames;
using Styx.CommonBot.Routines;
using Styx.Helpers;
using Styx.Patchables;
using Styx.Pathing;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using Action = Styx.TreeSharp.Action;
#if USE_DUNGEONBUDDY_DLL
using Bots.DungeonBuddyDll;
using Bots.DungeonBuddyDll.Profiles;
using Bots.DungeonBuddyDll.Attributes;
using Bots.DungeonBuddyDll.Helpers;
using AvoidanceManager = Bots.DungeonBuddyDll.Avoidance.AvoidanceManager;

namespace Bots.DungeonBuddyDll.Dungeon_Scripts.Mists_of_Pandaria
#else
    using Bots.DungeonBuddy.Profiles;
    using Bots.DungeonBuddy.Attributes;
    using Bots.DungeonBuddy.Helpers;
    using AvoidanceManager = Bots.DungeonBuddy.Avoidance.AvoidanceManager;
namespace Bots.DungeonBuddy.Dungeon_Scripts.Mists_of_Pandaria
#endif
{

    #region Normal Difficulty

    public class TheSecretsOfRagefire : Dungeon
    {
        #region Overrides of Dungeon

        public override uint DungeonId
        {
            get { return 649; }
        }

        public override void RemoveTargetsFilter(List<WoWObject> units)
        {
            units.RemoveAll(
                ret =>
                    {
                        var unit = ret.ToUnit();
                        if (unit != null)
                        {
                            if ((unit.Entry == OverseerElagloId || unit.Entry == KorkronDireSoldierId) && unit.CurrentTargetGuid == Me.Guid && Me.IsMelee() &&
                                (Me.HealthPercent < 70 || unit.HasAura(DireRageAuraId) && unit.GetAuraById(DireRageAuraId).StackCount >= 4) && ScriptHelpers.Healer == null)
                                return true;
                            //if (unit.Entry == OverseerElagloId && Me.IsMelee() && AvoidanceManager.Avoids.Any(a => a.IsPointInAvoid(unit.Location)))
                            //    return true;
                        }
                        return false;
                    });
        }

        public override void IncludeTargetsFilter(List<WoWObject> incomingunits, HashSet<WoWObject> outgoingunits)
        {
            foreach (var obj in incomingunits)
            {
                WoWUnit target;
                var unit = obj as WoWUnit;
                if (unit != null)
                {
                    if (unit.CurrentTargetGuid != 0 && (target = unit.CurrentTarget) != null && target.IsFriendly)
                        outgoingunits.Add(unit);
                    else if (unit.Entry == GlacialFreezeTotemId)
                        outgoingunits.Add(unit);
                }
            }
        }

        public override void WeighTargetsFilter(List<Targeting.TargetPriority> units)
        {
            foreach (var priority in units)
            {
                var unit = priority.Object as WoWUnit;
                if (unit != null)
                {
                    if (unit.Entry == PoisonBoltTotemId)
                        priority.Score += 4000;
                    else if (unit.Entry == GlacialFreezeTotemId)
                        priority.Score += 500000;
                }
            }
        }

        public override void OnExit()
        {
            if (appliedSpellBlacklist)
            {
                SpellBlacklist.Remove(_blacklistedSpells);
                appliedSpellBlacklist = false;
            }
        }

        private void ApplySpellBacklist()
        {
            SpellBlacklist.Apply(_blacklistedSpells);
            appliedSpellBlacklist = true;
        }

        #endregion

        private const uint PoisonBoltTotemId = 71334;
        private const uint KorkronDireSoldierId = 70665;
        private const uint KorkronDarkShamanId = 71245;
        private const int DireRageAuraId = 142760;
        private const uint GlacialFreezeTotemId = 71323;
        private bool appliedSpellBlacklist;

        private readonly int[] _blacklistedSpells =
        {
            6544, // Heroic Leap
            109132, // monk's roll.
            36554 // Shadowstep 
        };

        private static LocalPlayer Me
        {
            get { return StyxWoW.Me; }
        }


        [EncounterHandler(0)]
        public Composite RootEncounter()
        {

            // AddAvoidObject(ctx => Me.IsTank(), 10, o => o is WoWPlayer && !o.IsMe);

            WoWPoint jumpLoc;
            return new PrioritySelector(
                new Decorator(ctx => !appliedSpellBlacklist, new Action(ctx => ApplySpellBacklist())),
                // new Decorator(ctx => Me.CurrentTargetGuid != 0, new Action(ctx => Navigator.MoveTo(Me.CurrentTarget.Location))),
                new Decorator(
                    ctx =>
                    Targeting.Instance.TargetList.Any(
                        t =>
                        AvoidanceManager.IsRunningOutOfAvoid ||
                        (jumpLoc = Me.Location.RayCast(WoWMathHelper.NormalizeRadian(Me.Rotation), 25)).Distance(t.Location) <= 6 &&
                        !AvoidanceManager.Avoids.Any(a => a.IsPointInAvoid(jumpLoc))),
                    new Action(
                        ctx =>
                            {
                                Lua.DoString("ExtraActionButton1:Click()");
                                return RunStatus.Failure;
                            })));
        }


        [ScenarioStage(1)]
        public Composite StageOneEncounter()
        {
            ScenarioStage stage = null;
            var stage1Step1Loc = new WoWPoint(1419.482, 865.6567, 38.20304);
            var stage1Step2Loc = new WoWPoint(1433.922, 1001.52, 34.4494);
            const uint RuinedEarthId = 71262;
            WoWUnit detonator = null;
            const uint detonatorId = 70662;
            AddAvoidObject(ctx => true, 8, o => o.Entry == RuinedEarthId && o.ToUnit().HasAura(142310));

            return new PrioritySelector(
                ctx => stage = ctx as ScenarioStage,
                new Decorator(
                    ctx => !stage.GetStep(1).IsComplete,
                    new PrioritySelector(
                        ctx => detonator = ObjectManager.ObjectList.FirstOrDefault(o => o.Entry == detonatorId) as WoWUnit,
                        new Decorator(ctx => detonator != null, ScriptHelpers.CreateTalkToNpc(() => detonator)),
                        new Decorator(
                            ctx => detonator == null,
                            new PrioritySelector(
                                new Decorator(ctx => Me.Location.Distance(stage1Step1Loc) > 5, new Action(ctx => Navigator.MoveTo(stage1Step1Loc))),
                                new Decorator(
                                    ctx => Me.Location.Distance(stage1Step1Loc) <= 5,
                                    new PrioritySelector(new Decorator(ctx => Me.IsMoving, new Action(ctx => WoWMovement.MoveStop())), new ActionAlwaysSucceed())))))),
                new Decorator(ctx => stage.GetStep(1).IsComplete, new Action(ctx => ScriptHelpers.MoveTankTo(stage1Step2Loc))));
        }

        [ScenarioStage(2)]
        public Composite StageTwoEncounter()
        {
            var stage2Loc = new WoWPoint(1423.636, 1021.202, 34.41135);
            ScenarioStage stage = null;
            WoWUnit artifact = null;
            const uint ProtoDrakeEggsId = 71031;
            const uint SupplyCratesId = 70901;
            const uint PandariaArtifactsId = 71032;

            var artifactIds = new Dictionary<uint, int> {{ProtoDrakeEggsId, 1}, {SupplyCratesId, 2}, {PandariaArtifactsId, 3}};
            return new PrioritySelector(
                ctx =>
                    {
                        stage = ctx as ScenarioStage;
                        artifact = (from obj in ObjectManager.ObjectList.Where(o => artifactIds.ContainsKey(o.Entry))
                                    where !stage.GetStep(artifactIds[obj.Entry]).IsComplete
                                    let maxPartyToObjDist = Me.PartyMembers.Select(p => p.Location.DistanceSqr(obj.Location)).OrderByDescending(d => d).FirstOrDefault()
                                    orderby maxPartyToObjDist descending
                                    select obj).FirstOrDefault() as WoWUnit;
                        return stage;
                    },
                new Decorator(
                    ctx => artifact != null,
                    new PrioritySelector(
                        new Decorator(
                            ctx => artifact.WithinInteractRange,
                            new PrioritySelector(new Decorator(ctx => Me.IsMoving, new Action(ctx => WoWMovement.MoveStop())), new Action(ctx => artifact.Interact()))),
                        new Decorator(ctx => !artifact.WithinInteractRange, new Action(ctx => Navigator.MoveTo(artifact.Location))))),
                new Decorator(
                    ctx => artifact == null,
                    new PrioritySelector(
                        new Decorator(ctx => stage2Loc.Distance(Me.Location) > 5, new Action(ctx => ScriptHelpers.MoveTankTo(stage2Loc))),
                        new Decorator(ctx => stage2Loc.Distance(Me.Location) <= 5 && Me.IsTank(), new ActionAlwaysSucceed()))));
        }

        [ScenarioStage(3)]
        public Composite StageThreeEncounter()
        {
            var stage3Loc = new WoWPoint(1495.726, 1003.512, 38.44632);
            const uint CannonBallsId = 71176;
            const uint BrokenProtoDrakeEggId = 71197;
            const uint PoolPonyId = 71175;
            const uint BatteryId = 71195;
            WoWUnit part = null;
            const int carrySomethingId = 141842;

            var parts = new Dictionary<uint, int> {{CannonBallsId, 2}, {BatteryId, 3}, {PoolPonyId, 4}, {BrokenProtoDrakeEggId, 5}};

            ScenarioStage stage = null;
            return new PrioritySelector(
                ctx =>
                    {
                        stage = ctx as ScenarioStage;
                        part = (from obj in ObjectManager.ObjectList.Where(o => parts.ContainsKey(o.Entry))
                                where !stage.GetStep(parts[obj.Entry]).IsComplete
                                let maxPartyToObjDist = Me.PartyMembers.Select(p => p.Location.DistanceSqr(obj.Location)).OrderByDescending(d => d).FirstOrDefault()
                                orderby maxPartyToObjDist descending
                                select obj).FirstOrDefault() as WoWUnit;
                        return stage;
                    },
                new Decorator(
                    ctx => part != null && Targeting.Instance.IsEmpty(),
                    new PrioritySelector(
                        new Decorator(
                            ctx => !Me.HasAura(carrySomethingId),
                            new PrioritySelector(
                                new Decorator(
                                    ctx => part.WithinInteractRange,
                                    new PrioritySelector(new Decorator(ctx => Me.IsMoving, new Action(ctx => WoWMovement.MoveStop())), new Action(ctx => part.Interact()))),
                                new Decorator(ctx => !part.WithinInteractRange, new Action(ctx => Navigator.MoveTo(part.Location))))),
                        new Decorator(ctx => Me.HasAura(carrySomethingId), new Action(ctx => Navigator.MoveTo(stage3Loc))))),
                new Decorator(ctx => !stage.GetStep(1).IsComplete, new Action(ctx => ScriptHelpers.MoveTankTo(stage3Loc))));
        }

        private const uint OverseerElagloId = 71030;

        [ScenarioStage(4)]
        public Composite StageFourEncounter()
        {
            WoWUnit direSoldier = null;
            WoWUnit boss = null;
            var stage4Loc = new WoWPoint(1417.402, 1002.801, 34.00275);
            const uint shatteredEarthId = 71446;
            const uint electrifiedSpellId = 142338;
            const int DemolishArmorAuraId = 142764;

            AddAvoidObject(
                ctx => ScriptHelpers.Healer == null,
                () => stage4Loc,
                40,
                o => Me.IsMoving && Me.IsRange() ? 30 : 20,
                o =>
                o.Entry == OverseerElagloId && o.ToUnit().CurrentTargetGuid == Me.Guid &&
                (Me.HealthPercent < 70 || Me.HasAura(DemolishArmorAuraId) && Me.GetAuraById(DemolishArmorAuraId).StackCount >= 2));

            AddAvoidObject(
                ctx => ScriptHelpers.Healer == null,
                () => stage4Loc,
                40,
                o => Me.IsMoving && Me.IsRange() ? 30 : 20,
                o =>
                o.Entry == KorkronDireSoldierId && o.ToUnit().CurrentTargetGuid == Me.Guid &&
                (Me.HealthPercent < 70 || o.ToUnit().HasAura(DireRageAuraId) && o.ToUnit().GetAuraById(DireRageAuraId).StackCount >= 4));
            AddAvoidObject(ctx => true, 6, o => o.Entry == KorkronDarkShamanId && o.ToUnit().CastingSpellId == electrifiedSpellId);
            AddAvoidObject(ctx => true, 5, shatteredEarthId);

            return new PrioritySelector(
                ctx =>
                    {
                        boss = ObjectManager.ObjectList.FirstOrDefault(o => o.Entry == OverseerElagloId) as WoWUnit;
                        direSoldier = ObjectManager.GetObjectsOfType<WoWUnit>().FirstOrDefault(u => u.Entry == KorkronDireSoldierId && u.HasAura(DireRageAuraId));
                        return ctx;
                    },
                new Decorator(ctx => !appliedSpellBlacklist, new Action(ctx => ApplySpellBacklist())),
                new Decorator(ctx => direSoldier != null, ScriptHelpers.CreateDispellEnemy("Dire Rage", ScriptHelpers.EnemyDispellType.Enrage, () => direSoldier)),
                new Decorator(
                    ctx => Me.IsTank() && Targeting.Instance.IsEmpty() && stage4Loc.Distance(Me.Location) > 40 && !Me.Combat,
                    new Action(ctx => ScriptHelpers.MoveTankTo(stage4Loc))));
        }

        #region Nested type: SpellBlacklist

        private static class SpellBlacklist
        {
            // by highvoltz - hackish method to stop combat routines from casting certain spells.
            private const string BlacklistSpellLuaFormat = @"
if spellBlacklist == nil then spellBlacklist={{}} end
local ids = ""{0}""
for id in string.gmatch(ids, ""%d+"") do
    local name = GetSpellInfo(tonumber(id))
    if name ~= nil then spellBlacklist[name]=true end
end
if  originalIsUsableSpell == nil then
    originalIsUsableSpell = IsUsableSpell
    IsUsableSpell = function (spell, bookType)
        if type(spell)=='string' and spellBlacklist[spell] then
            return nil, nil
        end
        return originalIsUsableSpell(spell, bookType)
    end
end";

            private const string RemoveSpellBlacklistLuaFormat = @"
if spellBlacklist == nil then return end
local ids = ""{0}""
for id in string.gmatch(ids, ""%d+"") do
    local name = GetSpellInfo(tonumber(id))
    if name ~= nil and spellBlacklist[name] then spellBlacklist[name]=nil end
end
local cnt = 0 
for _, _ in pairs(spellBlacklist) do cnt = cnt + 1 end
if cnt == 0 then
    IsUsableSpell = originalIsUsableSpell
    originalIsUsableSpell = nil
    spellBlacklist = nil
end
";

            public static void Apply(params int[] spellIds)
            {
                var idsString = spellIds.Select(i => i.ToString(CultureInfo.InvariantCulture)).Aggregate((a, b) => a + "," + b);
                Lua.DoString(string.Format(BlacklistSpellLuaFormat, idsString));
            }

            public static void Remove(params int[] spellIds)
            {
                var idsString = spellIds.Select(i => i.ToString(CultureInfo.InvariantCulture)).Aggregate((a, b) => a + "," + b);
                Lua.DoString(string.Format(RemoveSpellBlacklistLuaFormat, idsString));
            }

            public static bool IsBlacklisted(int spellId)
            {
                return Lua.GetReturnVal<bool>(string.Format("local name = GetSpellInfo({0}) return name and spellBlacklist and spellBlacklist[name]", spellId), 0);
            }
        }

        #endregion
    }

    #endregion

    #region Heroic Difficulty

    public class TheSecretsOfRagefireHeroic : TheSecretsOfRagefire
    {
        #region Overrides of Dungeon

        public override uint DungeonId
        {
            get { return 625; }
        }

        #endregion

    }

    #endregion

}